package com.rookie.im.server;

import com.rookie.im.codec.WebSocketMessageDecoder;
import com.rookie.im.server.config.AppConfig;
import com.rookie.im.server.handler.HeartBeatServerHandler;
import com.rookie.im.server.handler.ServerHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

@Slf4j
public class ImServer {
    private final EventLoopGroup bossGroup;
    private final EventLoopGroup workerGroup;
    private int port;
    private Channel channel;

    public ImServer(AppConfig.WebsocketConfig config) {
//        loadConfig();
        port = config.getPort();
        bossGroup = new NioEventLoopGroup(config.getBoosThreadSize());
        workerGroup = new NioEventLoopGroup(config.getWorkerThreadSize());
    }

    public void run() {
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 10240) // 最大挂起队列长度
                    .option(ChannelOption.SO_REUSEADDR, true) // 地址复用
                    .childOption(ChannelOption.TCP_NODELAY, true) //
                    .childOption(ChannelOption.SO_KEEPALIVE, true) // 长连接
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            // websocket 基于http协议，所以要有http编解码器
                            pipeline.addLast("http-code", new HttpServerCodec());
                            // 对写大数据流的支持
                            pipeline.addLast("http-chunk", new ChunkedWriteHandler());
                            pipeline.addLast("aggregator", new HttpObjectAggregator(65535)); // 分离http对象
                            pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
                            pipeline.addLast(new HeartBeatServerHandler());
                            pipeline.addLast(new WebSocketMessageDecoder());
                            pipeline.addLast(new ServerHandler());

                        }
                    });
            // 同步阻塞等待bing完成
            channel = bootstrap.bind(port).sync().channel();
            System.out.println("服务启动");
            // 发生通知jvm异步回收资源
            Runtime.getRuntime().addShutdownHook(new Thread(this::shutdown));

            // 等待服务关闭完成
            channel.closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
            Thread.currentThread().interrupt();
        } finally {
            shutdown();
        }
    }

    /**
     * 释放资源
     */
    private void shutdown() {
        if (channel != null) {
            channel.close();
        }
        if (bossGroup != null) {
            bossGroup.shutdownGracefully();
        }
        if (workerGroup != null) {
            workerGroup.shutdownGracefully();
        }
    }

    public void loadConfig() {
        Properties properties = new Properties();
        try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream("application.properties")) {
            if (inputStream == null) {
                log.error("Falied to read application.properties");
                return;
            }
            properties.load(inputStream);
            port = Integer.parseInt(properties.getProperty("websocket.port"));
        }catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
